package io.gitee.chearnee.fw.common.utils;

import cn.hutool.json.JSONUtil;
import io.gitee.chearnee.fw.common.entity.BaseTree;
import io.gitee.chearnee.fw.common.model.IBaseTree;
import lombok.experimental.UtilityClass;
import org.springframework.util.CollectionUtils;

import java.util.*;
import java.util.function.Consumer;
import java.util.stream.Collectors;

/**
 * @author cn
 * @version 1.0
 * @description
 * @date 2022/4/6 17:41
 */
@UtilityClass
public class TreeUtil {

  /**
   * 合并两个树 极大合并<p/>
   * 这里以新树为基础：<br/> 同层级树：  <br/>
   * 1).如果新树的当前节点在旧树中存在，则直接使用新树的当前节点，并保持当前节点的顺序不变<br/> 2).如果新树的当前节点在旧树中存在,在 1)的基础上，去递归比较新树的子节点<br/>
   * 3).如果新树的当前节点在旧树中不存在，直接使用新树的当前节点，并保持当前节点的顺序不变<br/> 4).如果旧树的节点在新树中不存在，则直接附加到新树之后,并保持旧树的顺序<br/>
   * <p/>
   * snabbdom：https://github.com/snabbdom/snabbdom/blob/master/src/init.ts#L277
   *
   * @param oldTree
   * @param newTree
   * @return
   */
  public static <T extends IBaseTree<T,O>,O> List<T> largeMergeTree(List<T> oldTree, List<T> newTree) {
    return  largeMergeTree(oldTree, newTree, null);
  }

  /**
   * 合并树
   * @param oldTree
   * @param newTree
   * @param oldTreeConsumer 对需要附加到新树的节点进行操作
   * @param <T>
   * @return
   */
  public static <T extends IBaseTree<T,O>,O> List<T> largeMergeTree(List<T> oldTree, List<T> newTree,
      Consumer<List<T>> oldTreeConsumer) {
    //为空直接返回
    if (CollectionUtils.isEmpty(oldTree)) {
      return newTree;
    }
    if (CollectionUtils.isEmpty(newTree)) {
      return oldTree;
    }
    //构造老树code与所在索引的map
    Map<O, Integer> oldTreeIndexMap = new HashMap<>(oldTree.size());
    for (int i = 0; i < oldTree.size(); i++) {
      oldTreeIndexMap.put(oldTree.get(i).getNodeCode(), i);
    }

    //循环新树，找到新树中的当前节点在老树中的索引
    for (T t : newTree) {
      Integer oldTreeIndex = oldTreeIndexMap.get(t.getNodeCode());
      //找到了，则直接使用新树的当前节点，并保持当前节点的顺序不变
      if (oldTreeIndex != null) {
        t.setNodeChildren(
            largeMergeTree(oldTree.get(oldTreeIndex).getNodeChildren(), t.getNodeChildren(),
                oldTreeConsumer));
        oldTreeIndexMap.put(t.getNodeCode(), null);
      }
    }
    List<T> tempOldTree = null;
    if(oldTreeConsumer!=null){
      tempOldTree=new ArrayList<>();
    }
    List<T> finalResult = tempOldTree;
    //将所有老树中未找到的节点附加到新树之后,并保持旧树的顺序
    oldTreeIndexMap.values().stream().filter(Objects::nonNull).sorted().forEach(e-> {
      T t = oldTree.get(e);
      newTree.add(t);
      if (oldTreeConsumer != null) {
        finalResult.add(t);
      }
    });
    if (oldTreeConsumer != null) {
      oldTreeConsumer.accept(tempOldTree);
    }
    return newTree;
  }

  public static <T extends IBaseTree<T, O>, O> List<T> buildTree(List<T> treeList) {
    if (CollectionUtils.isEmpty(treeList)) {
      return treeList;
    }
    Set<O> codes = treeList.stream().map(IBaseTree::getNodeCode).collect(Collectors.toSet());
    List<T> rootList = treeList.stream().filter(e -> !codes.contains(e.getParentNodeCode())).toList();
    List<T> children = treeList.stream().filter(e -> codes.contains(e.getParentNodeCode())).toList();
    for (T t : rootList) {
      t.setNodeChildren(buildTree(children, t.getNodeCode()));
    }
    return rootList;
  }

  public static <T extends IBaseTree<T, O>, O> List<T> buildTree(List<T> treeList, O rootCode) {
    if (CollectionUtils.isEmpty(treeList)) {
      return treeList;
    }
    //root级树
    List<T> rootList = treeList.stream()
            .filter(e -> Objects.equals(e.getParentNodeCode(), rootCode)).toList();
    List<T> childrenList = treeList.stream()
            .filter(e -> !Objects.equals(e.getParentNodeCode(), rootCode)).toList();
    if (CollectionUtils.isEmpty(childrenList)) {
      return rootList;
    }
    for (T t : rootList) {
      t.setNodeChildren(buildTree(childrenList, t.getNodeCode()));
    }
    return rootList;
  }

  public static <T extends IBaseTree<T, O>, O> void flatTree(List<T> treeList, List<T> resultList) {
    if (CollectionUtils.isEmpty(treeList)) {
      return;
    }
    for (T t : treeList) {
      if (CollectionUtils.isEmpty(t.getNodeChildren())) {
        continue;
      }
      flatTree(t.getNodeChildren(), resultList);
//      t.setNodeChildren(new ArrayList<>());
    }
    resultList.addAll(treeList);
  }

  public static <T extends IBaseTree<T, O>, O> List<T> flatTree(List<T> treeList) {
    List<T> resultList = new ArrayList<>();
    if (!CollectionUtils.isEmpty(treeList)) {
      flatTree(treeList, resultList);
    }
    return resultList;
  }

  //  public static <T extends IBaseTree<T, O>, O> List<T> buildFastTree(List<T> treeList, O rootCode) {
//    if (CollectionUtils.isEmpty(treeList)) {
//      return treeList;
//    }
//    //root级树
//    List<T> rootList = treeList.stream().filter(e -> Objects.equals(e.getParentCode(), rootCode))
//        .collect(Collectors.toList());
//
//    List<T> notRootList = treeList.stream().filter(e -> !Objects.equals(e.getParentCode(), rootCode))
//        .collect(Collectors.toList());
//
//    Map<O, List<T>> childrenMap = notRootList.stream()
//        .collect(Collectors.groupingBy(IBaseTree::getParentCode));
//
//    for (T t : rootList) {
//      List<T> children = childrenMap.get(t.getCode());
//      if(children==null){
//        children=new ArrayList<>();
//      }
//      t.setChildren(children);
//    }
//    return rootList;
//  }
  public static void main(String[] args) {
    List<BaseTree> tempList = new ArrayList<>();
    BaseTree baseTree1 = new BaseTree();
    baseTree1.setCode("1");
    baseTree1.setName("a");
    baseTree1.setParentCode("");
    tempList.add(baseTree1);

    BaseTree baseTree2 = new BaseTree();
    baseTree2.setCode("2");
    baseTree2.setName("b");
    baseTree2.setParentCode("");
    tempList.add(baseTree2);


    BaseTree baseTree3 = new BaseTree();
    baseTree3.setCode("3");
    baseTree3.setName("c");
    baseTree3.setParentCode("1");
    tempList.add(baseTree3);

    BaseTree baseTree4 = new BaseTree();
    baseTree4.setCode("4");
    baseTree4.setName("d");
    baseTree4.setParentCode("2");
    tempList.add(baseTree4);

    BaseTree baseTree5 = new BaseTree();
    baseTree5.setCode("5");
    baseTree5.setName("e");
    baseTree5.setParentCode("3");
    tempList.add(baseTree5);
    List<BaseTree> baseTrees = buildTree(tempList, "");
    System.out.println(JSONUtil.toJsonStr(baseTrees));
    ArrayList<BaseTree> objects = new ArrayList<>();
    flatTree(baseTrees, objects);
    System.out.println(JSONUtil.toJsonStr(objects));

  }




}
